@(diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo],
  repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
  newCommitId: Option[String],
  oldCommitId: Option[String],
  showIndex: Boolean,
  issueId: Option[Int],
  hasWritePermission: Boolean,
  showLineNotes: Boolean)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@import org.eclipse.jgit.diff.DiffEntry.ChangeType
<style>
  th.line-num {
    cursor: pointer;
  }
</style>
@if(showIndex){
  <div class="pull-right" style="margin-bottom: 10px;">
    @if(oldCommitId.isEmpty && newCommitId.isDefined) {
      <a href="@helpers.url(repository)/patch/@newCommitId" class="btn btn-default">Patch</a>
    }
    @if(oldCommitId.isDefined && newCommitId.isDefined) {
      <a href="@helpers.url(repository)/patch/@oldCommitId...@newCommitId" class="btn btn-default">Patch</a>
    }
    <div class="btn-group">
      <a href="javascript:changeDisplaySetting('diff', 'unified');" id="btn-unified" class="btn btn-default">Unified</a>
      <a href="javascript:changeDisplaySetting('diff', 'split');" id="btn-split" class="btn btn-default">Split</a>
    </div>
  </div>
  Showing <a href="javascript:void(0);" id="toggle-file-list">@diffs.size changed @helpers.plural(diffs.size, "file")</a>
  <ul id="commit-file-list" style="display: none;">
  @diffs.zipWithIndex.map { case (diff, i) =>
    <li class="border">
      <span class="pull-right diffstat" data-diff-id="@i"></span>
      <a href="#diff-@i">
        @if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
          <i class="octicon octicon-diff-renamed"></i> @diff.oldPath -> @diff.newPath
        }
        @if(diff.changeType == ChangeType.ADD){
          <i class="octicon octicon-diff-added"></i> @diff.newPath
        }
        @if(diff.changeType == ChangeType.MODIFY){
          <i class="octicon octicon-diff-modified"></i> @diff.newPath
        }
        @if(diff.changeType == ChangeType.DELETE){
          <i class="octicon octicon-diff-removed"></i> @diff.oldPath
        }
      </a>
    </li>
    }
  </ul>
}
@diffs.zipWithIndex.map { case (diff, i) =>
  <a name="diff-@i"></a>
  <table class="table table-bordered diff-outside" commitId="@newCommitId" fileName="@diff.newPath" data-diff-id="@i">
    <tr>
      <th style="font-weight: normal;" class="box-header">
        <i class="fa fa-chevron-down rotate" data-toggle="collapse" data-target=".diff-collapse-@i" aria-hidden="true" style="cursor: pointer; font-size: 12px"></i>
        @if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
          @if(newCommitId.isDefined){
            <div class="pull-right align-right">
              <label class="no-margin"><input type="checkbox" class="ignore-whitespace" value="1"/> Ignore Space</label>
              <label class="no-margin"><input type="checkbox" class="toggle-notes" checked> Show notes</label>
              <a href="@helpers.url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-default btn-sm" title="View the whole file at version @newCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a>
            </div>
          }
          <span class="diffstat"><i class="octicon octicon-diff-renamed"></i></span>
          <a href="#diff-@helpers.md5(diff.newPath)" id="diff-@helpers.md5(diff.newPath)" class="file-hash"><span class="strong">@diff.oldPath → @diff.newPath</span></a>
        }
        @if(diff.changeType == ChangeType.ADD || diff.changeType == ChangeType.MODIFY){
          @if(newCommitId.isDefined){
            <div class="pull-right align-right">
              <label class="no-margin"><input type="checkbox" class="ignore-whitespace" value="1"/> Ignore Space</label>
              <label class="no-margin"><input type="checkbox" class="toggle-notes" checked> Show notes</label>
              <a href="@helpers.url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-default btn-sm" title="View the whole file at version @newCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a>
            </div>
          }
          <span class="diffstat">
          @if(diff.changeType == ChangeType.ADD){
            <i class="octicon octicon-diff-added"></i>
          } else {
            <i class="octicon octicon-diff-modified"></i>
          }
          </span>
          <a href="#diff-@helpers.md5(diff.newPath)" id="diff-@helpers.md5(diff.newPath)" class="file-hash"><span class="strong">@diff.newPath</span></a>
        }
        @if(diff.changeType == ChangeType.DELETE){
          @if(oldCommitId.isDefined){
            <div class="pull-right align-right">
              <label class="no-margin"><input type="checkbox" class="toggle-notes" checked> Show notes</label>
              <a href="@helpers.url(repository)/blob/@oldCommitId.get/@diff.oldPath" class="btn btn-default btn-sm" title="View the whole file at version @oldCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a>
            </div>
          }
          <span class="diffstat"><i class="octicon octicon-diff-removed"></i></span>
          <a href="#diff-@helpers.md5(diff.oldPath)" id="diff-@helpers.md5(diff.oldPath)" class="file-hash"><span class="strong">@diff.oldPath</span></a>
        }
        @if(diff.oldMode != diff.newMode){
          <span class="monospace">@diff.oldMode → @diff.newMode</span>
        }
      </th>
    </tr>
    <tr class="diff-collapse-@i collapse in">
      <td style="padding: 0;">
        @if(diff.oldObjectId == diff.newObjectId){
          @if(diff.oldPath != diff.newPath){
            <div class="diff-same">File renamed without changes</div>
          } else {
            <div class="diff-same">File mode changed</div>
          }
        } else {
          @if(diff.newContent != None || diff.oldContent != None){
            <div id="diffText-@i" class="diffText"></div>
            <input type="hidden" id="newText-@i" data-file-name="@diff.newPath" data-val="@diff.newContent">
            <input type="hidden" id="oldText-@i" data-file-name="@diff.oldPath" data-val="@diff.oldContent">
          } else {
            @if(diff.newIsImage || diff.oldIsImage){
              <div class="diff-image-render diff2up">
              @if(oldCommitId.isDefined && diff.oldIsImage){
                <div class="diff-image-frame diff-old"><img src="@helpers.url(repository)/raw/@oldCommitId.get/@diff.oldPath" class="diff-image" onload="onLoadedDiffImages(this)" style="display:none" /></div>
              } else {
                @if(diff.changeType != ChangeType.ADD){
                  <div style="padding: 12px;">Not supported</div>
                }
              }
              @if(newCommitId.isDefined && diff.newIsImage){
                <div class="diff-image-frame diff-new"><img src="@helpers.url(repository)/raw/@newCommitId.get/@diff.newPath" class="diff-image" onload="onLoadedDiffImages(this)" style="display:none" /></div>
              } else {
                @if(diff.changeType != ChangeType.DELETE){
                  <div style="padding: 12px;">Not supported</div>
                }
              }
              </div>
            } else {
              @if(diff.tooLarge){
                <div style="padding: 12px;">Too large</div>
              } else {
                <div style="padding: 12px;">Not supported</div>
              }
            }
          }
        }
      </td>
    </tr>
  </table>
}
<script type="text/javascript" src="@helpers.assets("/vendors/jsdifflib/difflib.js")"></script>
<link href="@helpers.assets("/vendors/jsdifflib/diffview.css")" type="text/css" rel="stylesheet" />
<script>

$(".rotate").click(function(){
    if($(this).hasClass('fa-chevron-right')) {
      $(this).removeClass('fa-chevron-right');
      $(this).addClass('fa-chevron-down');
    } else {
      $(this).removeClass('fa-chevron-down');
      $(this).addClass('fa-chevron-right');
    }
});

$(function(){
  @if(showIndex){
    $('#toggle-file-list').click(function(){
      $('#commit-file-list').toggle();
      if($(this).val() == 'Show file list'){
        $(this).val('Hide file list');
      } else {
        $(this).val('Show file list');
      }
    });
  }

  window.params = {};

  // Render diffs as unified mode initially
  if(("&" + location.search.substring(1)).indexOf("&w=1") != -1){
    $('.ignore-whitespace').prop('checked', true);
    window.params['w'] = 1;
  }
  if(("&" + location.search.substring(1)).indexOf("&diff=split") != -1){
    window.viewType = 0;
    $('#btn-split').addClass('active');
    window.params['diff'] = 'split';
  } else {
    window.viewType = 1;
    $('#btn-unified').addClass('active');
  }
  renderDiffs();

  window.onhashchange = function(){
    updateHighlighting();
  }

  $('.toggle-notes').change(function() {
    if (!$(this).prop('checked')) {
      $(this).closest('table').find('.not-diff.inline-comment-form').remove();
    }
    $(this).closest('table').find('.not-diff').toggle();
  });

  $('.ignore-whitespace').change(function() {
    renderOneDiff($(this).closest("table").find(".diffText"), window.viewType, $(this).closest("table").find(".file-hash")[0].id);
  });

  function getInlineContainer(where) {
    if (window.viewType == 0) {
      if (where === 'new') {
        return $('<tr class="not-diff"><td colspan="2"></td><td colspan="2" class="comment-box-container"></td></tr>');
      } else {
        return $('<tr class="not-diff"><td colspan="2" class="comment-box-container"></td><td colspan="2"></td></tr>');
      }
    }
    return $('<tr class="not-diff"><td colspan="3" class="comment-box-container"></td></tr>');
  }

  if (typeof $('#show-notes')[0] !== 'undefined' && !$('#show-notes')[0].checked) {
    $('#comment-list').children('.inline-comment').hide();
  }

  function showCommentForm(commitId, fileName, oldLineNumber, newLineNumber, $tr){
    // assemble Ajax url
    let url = '@helpers.url(repository)/commit/' + commitId + '/comment/_form?fileName=' + fileName@issueId.map { id => + '&issueId=@id' };
    if (!isNaN(oldLineNumber) && oldLineNumber) {
      url += ('&oldLineNumber=' + oldLineNumber)
    }
    if (!isNaN(newLineNumber) && newLineNumber) {
      url += ('&newLineNumber=' + newLineNumber)
    }
    // send Ajax request
    $.get(url, { dataType : 'html' }, function(responseContent) {
      // create container
      let tmp;
      if (!isNaN(oldLineNumber) && oldLineNumber) {
        if (!isNaN(newLineNumber) && newLineNumber) {
          tmp = getInlineContainer();
        } else {
          tmp = getInlineContainer('old');
        }
      } else {
        tmp = getInlineContainer('new');
      }
      // add comment textarea
      tmp.addClass('inline-comment-form').children('.comment-box-container').html(responseContent);
      $tr.nextAll(':not(.not-diff):first').before(tmp);
      // hide reply comment field
      $(tmp).closest('.not-diff').prev().find('.reply-comment').closest('.not-diff').hide();
      // focus textarea
      tmp.find('textarea').focus();
    });
  }

  // Add comment button
  $('.diff-outside').on('click','table.diff .add-comment',function() {
    const $this  = $(this);
    const $tr    = $this.closest('tr');
    const $check = $this.closest('table:not(.diff)').find('.toggle-notes');
    if (!$check.prop('checked')) {
      $check.prop('checked', true).trigger('change');
    }
    if (!$tr.nextAll(':not(.not-diff):first').prev().hasClass('inline-comment-form')) {
      const commitId = $this.closest('.table-bordered').attr('commitId'),
      fileName = $this.closest('.table-bordered').attr('fileName');
      let oldLineNumber, newLineNumber;
      if (window.viewType == 0) {
        oldLineNumber = $this.parent().prev('.oldline').attr('line-number');
        newLineNumber = $this.parent().prev('.newline').attr('line-number');
      } else {
        oldLineNumber = $this.parent().prevAll('.oldline').attr('line-number');
        newLineNumber = $this.parent().prevAll('.newline').attr('line-number');
      }

      showCommentForm(commitId, fileName, oldLineNumber, newLineNumber, $tr);
    }
  }).on('click', 'table.diff .btn-default', function() {
    // Cancel comment form
    $(this).closest('.not-diff').prev().find('.reply-comment').closest('.not-diff').show();
    $(this).closest('.inline-comment-form').remove();
  });

  // Reply comment
  $('.diff-outside').on('click', '.reply-comment',function(){
    const $this = $(this);
    const $tr    = $this.closest('tr');
    const commitId = $this.closest('.table-bordered').attr('commitId');
    const fileName = $this.data('filename');
    const oldLineNumber = $this.data('oldline');
    const newLineNumber = $this.data('newline');

    showCommentForm(commitId, fileName, oldLineNumber, newLineNumber, $tr);
  });

  // Line selection
  $('.diff-outside').on('click','table.diff th.line-num',function(e) {
    const $this   = $(this);
    const hash    = location.hash;
    const baseUrl = location.toString().split("#")[0];

    if (e.shiftKey == true && hash.match(/#diff-\w+-L\d+(-L\d+)?/)) {
      const fragments = hash.split('-');
      window.history.pushState('', '', baseUrl + '#diff-' + fragments[1] + '-' + fragments[2] + '-' + $this[0].id.split('-')[2]);
    } else {
      window.history.pushState('', '', baseUrl + '#' + $this[0].id);
    }
    getSelection().empty();
    updateHighlighting();
  });

  function renderOneCommitCommentIntoDiff($v, diff){
    //var filename = $v.attr('filename');
    const oldline = $v.attr('oldline');
    const newline = $v.attr('newline');
    let tmp;
    if (typeof oldline !== 'undefined') {
      if (typeof newline !== 'undefined') {
        tmp = getInlineContainer();
      } else {
        tmp = getInlineContainer('old');
      }
      tmp.children('td:first').html($v.clone().show());
      diff.find('table.diff').find('.oldline[line-number=' + oldline  + ']').parent().nextAll(':not(.not-diff):first').before(tmp);
    } else {
      tmp = getInlineContainer('new');
      tmp.children('td:last').html($v.clone().show());
      diff.find('table.diff').find('.newline[line-number=' + newline  + ']').parent().nextAll(':not(.not-diff):first').before(tmp);
    }
    if (!diff.find('.toggle-notes').prop('checked')) {
      tmp.hide();
    }
  }

  function renderStatBar(add, del){
    if(add + del > 5){
      if(add){
        if(add < del){
          add = Math.floor(1 + (add * 4 / (add + del)));
        } else {
          add = Math.ceil(1 + (add * 4 / (add + del)));
        }
      }
      del = 5 - add;
    }
    const ret = $('<div class="diffstat-bar">');
    for(let i = 0; i < 5; i++){
      if(add){
        ret.append('<span class="text-diff-added">■</span>');
        add--;
      } else if(del){
        ret.append('<span class="text-diff-deleted">■</span>');
        del--;
      } else {
        ret.append('■');
      }
    }
    return ret;
  }

  function renderOneDiff(diffText, viewType, fileHash){
    const table = diffText.closest("table[data-diff-id]");
    const i = table.data("diff-id");
    const ignoreWhiteSpace = table.find('.ignore-whitespace').prop('checked');
    diffUsingJS('oldText-' + i, 'newText-' + i, diffText.attr('id'), viewType, ignoreWhiteSpace, fileHash);
    const add = diffText.find("table").attr("add") * 1;
    const del = diffText.find("table").attr("del") * 1;
    table.find(".diffstat").text(add + del + " ").append(renderStatBar(add, del)).attr("title", add + " additions & " + del + " deletions").tooltip();
    $('span.diffstat[data-diff-id="' + i + '"]')
        .html('<span class="text-diff-added">+' + add + '</span><span class="text-diff-deleted">-' + del + '</span>')
        .append(renderStatBar(add, del).attr('title', (add + del) + " lines changed").tooltip());

    @if(hasWritePermission) {
      diffText.find('.body').filter(function(i, e) {
        return $(e).has('span').length > 0;
      }).each(function(){
        $('<b class="add-comment">+</b>').prependTo(this);
      });
    }
    @if(showLineNotes){
      const fileName = table.attr('filename');
      $('.inline-comment').each(function(i, v) {
        if($(this).attr('filename') == fileName){
          renderOneCommitCommentIntoDiff($(this), table);
        }
      });
    }
    return table;
  }

  function renderReplyComment($table){
    const elements = {};
    let filename, newline, oldline;
    $table.find('.comment-box-container .inline-comment').each(function(i, e){
      filename = $(e).attr('filename');
      newline = $(e).attr('newline');
      oldline = $(e).attr('oldline');
      const key = filename + '-' + newline + '-' + oldline;
      elements[key] = {
        element: $(e),
        filename: filename,
        newline: newline,
        oldline: oldline
      };
    });
    for(const key in elements){
      filename = elements[key]['filename'];
      oldline = elements[key]['oldline']; //? elements[key]['oldline'] : '';
      newline = elements[key]['newline']; //? elements[key]['newline'] : '';

      const $v = $('<div class="commit-comment-box reply-comment-box">')
        .append($('<input type="text" class="form-control reply-comment" placeholder="Reply..." '
                       + 'data-filename="' + filename + '" '
                       + 'data-newline="' + newline + '" '
                       + 'data-oldline="' + oldline + '">'));

      let tmp;
      if (typeof oldline !== 'undefined') {
        if (typeof newline !== 'undefined') {
          tmp = getInlineContainer();
        } else {
          tmp = getInlineContainer('old');
        }
        tmp.children('td:first').html($v);
      } else {
        tmp = getInlineContainer('new');
        tmp.children('td:last').html($v);
      }
      elements[key]['element'].closest('.not-diff').after(tmp);
    }
  }

  function renderDiffs() {
    const diffs = $('.diffText');
    let i = 0;
    function render(){
      if (diffs[i]) {
        const $table = renderOneDiff($(diffs[i]), window.viewType, $('.file-hash')[i].id);
        @if(hasWritePermission) {
          renderReplyComment($table);
        }
        i++;
        setTimeout(render);
      } else {
        updateHighlighting();
      }
    }
    render();
  }
});

function changeDisplaySetting(key, value){
  let url = '';
  window.params[key] = value;
  for(key in window.params){
    if(window.params[key] != ''){
      if(url != ''){
        url = url + '&' + key + '=' + window.params[key];
      } else {
        url = url + '?' + key + '=' + window.params[key];
      }
    }
  }
  location.href = url;
}

let scrolling = false;

/**
 * Highlight lines which are specified by URL hash.
 */
function updateHighlighting(){
  const hash = location.hash;
  $('tr.highlight').removeClass('highlight');
  if (hash.match(/#diff-(\w+)-[LR]\d+(-[LR]\d+)?/)) {
    const fragments = hash.substr(1).split('-');
    if (fragments.length == 3) {
      const tr = $('th#diff-' + fragments[1] + '-' + fragments[2]).closest('tr');
      tr.addClass('highlight');
      if(!scrolling){
        $(window).scrollTop($('th#diff-' + fragments[1] + '-' + fragments[2]).closest('tr').offset().top);
      }
    } else if (fragments.length > 3) {
      let highlight = false;
      $('th[id^=diff-' + fragments[1] + '-').each(function(i, th) {
        if (th.id.split('-')[2] == fragments[2]) { // start
          highlight = true;
          $(th.closest('tr')).addClass('highlight');
        } else if (highlight == true) {
          if (th.id.split('-')[2] == fragments[3]) { // end
            $(th.closest('tr')).addClass('highlight');
            return false;
          } else {
            $(th.closest('tr')).addClass('highlight');
          }
        }
      });
      if(!scrolling){
        $(window).scrollTop($('th#diff-' + fragments[1] + '-' + fragments[2]).closest('tr').offset().top);
      }
    }
  } else if (hash != ''){
    if (!scrolling) {
      $(window).scrollTop($(hash).offset().top);
    }
  }
  scrolling = true;
}
</script>
